package com.liyong.pullorder.sdk.autoconfigure;

import com.liyong.pullorder.sdk.PullOrderSdkService;
import com.liyong.pullorder.sdk.client.HttpClientConfig;
import com.liyong.pullorder.sdk.client.auth.ApiKeyStrategy;
import com.liyong.pullorder.sdk.client.auth.AuthStrategy;
import com.liyong.pullorder.sdk.client.auth.OAuth2Strategy;
import com.liyong.pullorder.sdk.parser.ParserStrategy;
import com.liyong.pullorder.sdk.retry.RetryConfig;
import com.liyong.pullorder.sdk.sentinel.SentinelConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.web.client.RestTemplate;

import java.util.List;

import static java.util.Arrays.asList;

/**
 * @author 葡萄味的萝卜
 * @date 2025/10/21 10:35
 * 拉单SDK自动配置类
 * 作用：Spring Boot自动装配的核心类，负责创建和配置所有必要的Bean
 * 条件注解说明（条件装配策略）：
 * - @ConditionalOnClass: 类路径下存在PullOrderSdkService时才生效，确保类路径下存在核心服务类
 * - @ConditionalOnProperty: 配置文件中pull.order.sdk.enabled=true时才生效，通过配置控制SDK启停
 * - @ConditionalOnMissingBean: 当容器中不存在该Bean时才创建，支持用户自定义覆盖
 *
 * 拉单SDK自动配置类 - Spring Boot Starter核心入口
 * 核心作用：实现开箱即用，使用者只需添加依赖和配置即可快速接入
 * 设计目标：将接入周期从2-3人周缩短至0.5人周，效率提升70%+
 */

@Configuration
@ConditionalOnClass(PullOrderSdkService.class)
@EnableConfigurationProperties(PullOrderSdkProperties.class)
@EnableRetry  // 启用Spring重试机制，实现指数退避重试
@ConditionalOnProperty(prefix = "pull-order-sdk", name = "enabled", havingValue = "true")
public class PullOrderSdkAutoConfiguration {
    /**
     * 配置HTTP客户端连接池
     * 解决痛点：各平台网络稳定性差异，提供统一连接管理
     */
    @Bean
    @ConditionalOnMissingBean
    public HttpClientConfig httpClientConfig(PullOrderSdkProperties properties) {
        return new HttpClientConfig(properties);
    }

    /**
     * 注册认证策略集合
     * 设计模式：策略模式，支持OAuth2、API key等多种认证方式
     * 扩展性：新增认证方式只需要实现AuthStrategy接口即可
     */
    @Bean
    @ConditionalOnMissingBean
    public List<AuthStrategy> authStrategies() {
       return asList(
               new OAuth2Strategy(),
               new ApiKeyStrategy()
       );
    }

    /**
     * 配置Sentinel限流降级
     * 保障系统：防止异常流量冲击，实现多级 fallback 保护
     * 监控告警：对接公司告警平台，实时通知拉单异常
     */
    @Bean
    @ConditionalOnMissingBean
    public SentinelConfig sentinelConfig(PullOrderSdkProperties properties) {
        return new SentinelConfig(properties);
    }

    /**
     * 创建用于拉单的 RestTemplate Bean。
     *
     * 设计意图：
     * - 统一在客户端侧收集关键调用指标（成功/失败、耗时、平台来源），便于后续埋点与告警。
     * - 保持拦截器尽量无侵入：不修改请求/响应实体，仅做旁路记录与透传。
     * - 失败场景也记录耗时与错误类型，辅助定位性能瓶颈与稳定性问题。
     */
    @Bean
    @ConditionalOnMissingBean
    public org.springframework.web.client.RestTemplate restTemplate(com.liyong.pullorder.sdk.client.HttpClientConfig httpClientConfig) {
        // 复用统一的 HTTP 客户端配置（连接池、超时、编码等）
        org.springframework.web.client.RestTemplate restTemplate = httpClientConfig.restTemplate();

        java.util.List<org.springframework.http.client.ClientHttpRequestInterceptor> interceptors = new java.util.ArrayList<>();
        interceptors.add((request, body, execution) -> {
            // 记录调用起始时间，用于计算本次请求耗时
            long startTime = System.currentTimeMillis();
            // 从 URL 中粗略提取平台标识（用于按平台分维度统计）
            String platform = extractPlatformFromUrl(request.getURI().toString());
            try {
                // 继续执行下一个拦截器或真实 HTTP 调用
                org.springframework.http.client.ClientHttpResponse response = execution.execute(request, body);
                long cost = System.currentTimeMillis() - startTime;
                // 成功场景：打印必要的审计日志，并记录成功指标
                org.slf4j.LoggerFactory.getLogger(com.liyong.pullorder.sdk.autoconfigure.PullOrderSdkAutoConfiguration.class)
                        .info("拉单请求成功 - 平台: {}, URL: {}, 耗时: {}ms", platform, request.getURI(), cost);
                MetricsCollector.recordRequestSuccess(platform, cost);
                return response; // 保持响应透传，不做额外处理
            } catch (Exception e) {
                long cost = System.currentTimeMillis() - startTime;
                // 失败场景：打印错误日志（含耗时与错误类型），并记录失败指标
                org.slf4j.LoggerFactory.getLogger(com.liyong.pullorder.sdk.autoconfigure.PullOrderSdkAutoConfiguration.class)
                        .error("拉单请求失败 - 平台: {}, URL: {}, 耗时: {}ms, 错误: {}", platform, request.getURI(), cost, e.getMessage());
                MetricsCollector.recordRequestFailure(platform, cost, e.getClass().getSimpleName());
                throw e; // 将异常继续抛出，交由上层重试/熔断等机制处理
            }
        });
        // 注册拦截器到 RestTemplate，使之在每次调用时被执行
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    }

    /**
     * 核心拉单服务Bean
     * 系统收口：作为公司供应链体系唯一订单入口
     * 处理能力：日均处理10万+订单数据请求
     */
    @Bean
    @ConditionalOnMissingBean
    public PullOrderSdkService (
            PullOrderSdkProperties properties,
            HttpClientConfig httpClientConfig,
            List<AuthStrategy> authStrategies,
            List<ParserStrategy> parserStrategies,
            SentinelConfig sentinelConfig,
            RetryConfig retryConfig) {
        return new PullOrderSdkService(properties, httpClientConfig.restTemplate(), authStrategies, parserStrategies, sentinelConfig, retryConfig);
    }

    @Bean
    @ConditionalOnMissingBean
    public RetryConfig retryConfig(PullOrderSdkProperties properties) {
        return new RetryConfig(properties);
    }

    @Bean
    @ConditionalOnMissingBean
    public List<ParserStrategy> parserStrategies() {
        return asList(
                new com.liyong.pullorder.sdk.parser.JsonParserStrategy(),
                new com.liyong.pullorder.sdk.parser.XmlParserStrategy()
        );
    }
}

/**
 * 从 URL 文本中提取平台标识（非严格，偏统计维度）。
 *
 * 说明：
 * - 仅进行简单包含匹配，不做复杂正则与域名解析，避免性能损耗。
 * - 若无法识别，返回 "UNKNOWN"，上层按未知平台归档指标即可。
 */
private String extractPlatformFromUrl(String url) {
    if (url == null) return "UNKNOWN";
    String lower = url.toLowerCase();
    if (lower.contains("huawei")) return "HUAWEI";
    if (lower.contains("honor")) return "HONOR";
    if (lower.contains("zjmobile")) return "ZJMOBILE";
    return "UNKNOWN";
}

/**
 * 轻量级指标采集器（示例实现）。
 *
 * 设计取舍：
 * - 仅打印 debug 日志，避免引入真实监控依赖；实际项目可替换为 Micrometer/Prometheus 等。
 * - 统一封装成功/失败记录，便于后续在单点替换为更强的监控实现。
 */
private static class MetricsCollector {
    static void recordRequestSuccess(String platform, long cost) {
        org.slf4j.LoggerFactory.getLogger(com.liyong.pullorder.sdk.autoconfigure.PullOrderSdkAutoConfiguration.class)
                .debug("记录成功指标 - 平台: {}, 耗时: {}ms", platform, cost);
    }
    static void recordRequestFailure(String platform, long cost, String errorType) {
        org.slf4j.LoggerFactory.getLogger(com.liyong.pullorder.sdk.autoconfigure.PullOrderSdkAutoConfiguration.class)
                .debug("记录失败指标 - 平台: {}, 耗时: {}ms, 错误类型: {}", platform, cost, errorType);
    }
}
